﻿using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Data;
using nRoute.Behaviors.Interactivity;
using System.ComponentModel;

namespace nRoute.Behaviors
{
    public class ExecuteCommandAction
		 : BindableTriggerAction<FrameworkElement>
    {
        private const double INTERACTIVITY_ENABLED = 1d;
        private const double INTERACTIVITY_DISABLED = 0.5d;

        public static readonly DependencyProperty CommandProperty = 
            DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommandAction), 
            new PropertyMetadata(null, new PropertyChangedCallback(OnCommandChanged)));

        public static readonly DependencyProperty ParameterProperty = 
            DependencyProperty.Register("Parameter", typeof(object), typeof(ExecuteCommandAction),
            new PropertyMetadata(null, new PropertyChangedCallback(OnParameterChanged)));

        public static readonly DependencyProperty TriggerParameterConverterProperty =
            DependencyProperty.Register("TriggerParameterConverter", typeof(IValueConverter), typeof(ExecuteCommandAction),
            new PropertyMetadata(null));

        private bool _manageEnableState;

#region Properties

        [Category("Common Properties")]
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        [Category("Common Properties")]
#if SILVERLIGHT
        [TypeConverter(typeof(nRoute.Components.TypeConverters.ConvertFromStringConverter))]
#endif
        public object Parameter
        {
            get { return GetValue(ParameterProperty); }
            set { SetValue(ParameterProperty, value); }
        }

        [Category("Common Properties")]
        public IValueConverter TriggerParameterConverter
        {
            get { return (IValueConverter)GetValue(TriggerParameterConverterProperty); }
            set { SetValue(TriggerParameterConverterProperty, value); }
        }

        [Category("Common Properties")]
        public bool ManageEnableState
        {
            get { return _manageEnableState; }
            set { _manageEnableState = value; }
        }

#if SILVERLIGHT

        [Category("Common Properties")]
        public Binding CommandBinding
        {
            get { return GetBinding(CommandProperty); }
            set { SetBinding<ICommand>(CommandProperty, value); }
        }

        [Category("Common Properties")]
        public Binding ParameterBinding
        {
            get { return GetBinding(ParameterProperty); }
            set { SetBinding<object>(ParameterProperty, value); }
        }

        [Category("Common Properties")]
        public Binding TriggerParameterConverterBinding
        {
            get { return GetBinding(TriggerParameterConverterProperty); }
            set { SetBinding<IValueConverter>(TriggerParameterConverterProperty, value); }
        }

#endif

#endregion

#region Trigger Related

        protected override void OnAttached()
        {
            base.OnAttached();
            UpdateEnabledState();
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            DisposeEnableState();
        }

        protected override void Invoke(object parameter)
        {
            if (this.AssociatedObject == null) return;

            // if a trigger parameter converter is specified, then we use that to get the command parameter
            // else we use the given parameter - note_ the parameter can be null
            var _parameter = TriggerParameterConverter != null ?
                TriggerParameterConverter.Convert(parameter, typeof(Object), this.AssociatedObject, CultureInfo.CurrentCulture) :
                this.Parameter;

            if (this.Command != null && this.Command.CanExecute(_parameter))
                this.Command.Execute(_parameter);
        }

#endregion
         
#region Handlers

        private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ExecuteCommandAction)d).SetupEnableState(e.NewValue as ICommand, e.OldValue as ICommand);
        }

        private static void OnParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ExecuteCommandAction)d).UpdateEnabledState();
        }

        private void Command_CanExecuteChanged(object sender, EventArgs e)
        {
            this.UpdateEnabledState();
        }

#endregion

#region Helpers

        private void SetupEnableState(ICommand newCommand, ICommand oldCommand)
        {
            if (!ManageEnableState) return;

            // we detach or attach
            if (oldCommand != null)
                oldCommand.CanExecuteChanged -= new EventHandler(Command_CanExecuteChanged);
            if (newCommand != null)
                newCommand.CanExecuteChanged += new EventHandler(Command_CanExecuteChanged);

            // and update
            UpdateEnabledState();
        }

        private void UpdateEnabledState()
        {
            if (!ManageEnableState || AssociatedObject == null || Command == null) return;

            // we get if it is enabled or not
            var _canExecute = this.Command.CanExecute(this.Parameter);

            // we check if it is a control or UIElement
#if (SILVERLIGHT)          
            if (typeof(Control).IsAssignableFrom(AssociatedObject.GetType()))
            {
                var _target = AssociatedObject as Control;
#else
            if (typeof(UIElement).IsAssignableFrom(AssociatedObject.GetType()))
            {
                var _target = AssociatedObject as UIElement;
#endif
                _target.IsEnabled = _canExecute;
            }
            else
            {
                AssociatedObject.IsHitTestVisible = _canExecute;
                AssociatedObject.Opacity = _canExecute ? INTERACTIVITY_ENABLED : INTERACTIVITY_DISABLED;
            }
        }

        private void DisposeEnableState()
        {
            if (!ManageEnableState || AssociatedObject == null || Command == null) return;

#if (SILVERLIGHT)          
            if (AssociatedObject as Control != null)
#else
            if (AssociatedObject as UIElement != null)
#endif
            {
                Command.CanExecuteChanged -= new EventHandler(Command_CanExecuteChanged);
            }
        }

#endregion

    }
}
